using System;
using System.Collections.Generic;
using CLELCore.Assembly.BlobStreams;
using CLELCore.Assembly.StreamTables;

namespace CLELCore.Assembly.Descriptors.ClassDescriptors
{
	public class ClassDescriptor
	{
		public const int BUILTIN_CLASSDESCRIPTOR = 0x0;
		public const int TYPEDEF_CLASSDESCRIPTOR = 0x1;
		public const int TYPEREF_CLASSDESCRIPTOR = 0x2;
		public const int TYPESPEC_CLASSDESCRIPTOR = 0x3;
		public const int GENERICTYPEDEF_CLASSDESCRIPTOR = 0x4;
		public const int GENERICTYPEREF_CLASSDESCRIPTOR = 0x5;
		public const int SZARRAY_CLASSDESCRIPTOR = 0x6;
		public const int MVAR_CLASSDESCRIPTOR = 0x7;
		public const int ARRAY_CLASSDESCRIPTOR = 0x8;
		public const int BYREF_CLASSDESCRIPTOR = 0x9;
	
		private int _type;

		public int Type
		{
			get
			{
				return _type;
			}
		}

		public ClassDescriptor(int type)
		{
			_type = type;
		}
		
		public static ClassDescriptor ParseMethodLocalTypeFromBlob(CLELAssembly assembly,byte [] blob,ref int start_offset)
		{
			return ParseClassDescriptor(assembly,blob,ref start_offset);
		}

		private static ClassDescriptor ParseClassDescriptor(CLELAssembly assembly,byte [] blob,ref int start_offset)
		{
			if(assembly == null)
				throw new ArgumentException("CLELAssembly cannot be null");
			if(blob == null)
				throw new ArgumentException("Blob array cannot be null");
			int len = blob.Length;
			if(start_offset >= len)
				throw new IndexOutOfRangeException("Index out of bounds in blob array");
			byte b = blob[start_offset];
			start_offset++;
			int decompress;
			int row;
			//for(int k = 0;k < blob.Length;k++)
			//	Console.Write(blob[k]+" ");
			//Console.WriteLine();
			uint i;
			switch(b)
			{
				//TODO: ADD REST OF CASES
				case BlobElementType.ELEMENT_TYPE_VOID:
				case BlobElementType.ELEMENT_TYPE_CHAR:
				case BlobElementType.ELEMENT_TYPE_U1:
				case BlobElementType.ELEMENT_TYPE_I4:
				case BlobElementType.ELEMENT_TYPE_I2:
				case BlobElementType.ELEMENT_TYPE_U4:
				case BlobElementType.ELEMENT_TYPE_U2:
				case BlobElementType.ELEMENT_TYPE_I8:
				case BlobElementType.ELEMENT_TYPE_R8:
				case BlobElementType.ELEMENT_TYPE_BOOLEAN:
				case BlobElementType.ELEMENT_TYPE_STRING:
				case BlobElementType.ELEMENT_TYPE_OBJECT:
					return new BuiltInClassDescriptor(b);
				case BlobElementType.ELEMENT_TYPE_CLASS: 
					decompress = (int)Blob.DecompressBlobValue(blob,ref start_offset); //get number of parameters
					row = decompress>>2;
					if(row > 0)
					{
						int type = decompress&0x3;
						if(type == 0) //TypeDef
						{
							return (ClassDescriptor)new TypeDefClassDescriptor(assembly,BlobElementType.ELEMENT_TYPE_CLASS,new Token(StreamTable.TYPEDEF_TABLE,row));
						}
						else if(type == 1) //TypeRef
						{
							return (ClassDescriptor)new TypeRefClassDescriptor(assembly,BlobElementType.ELEMENT_TYPE_CLASS,new Token(StreamTable.TYPEREF_TABLE,row));
						}
						else if(type == 2) //TypeSpec
						{
							//?????????
						}
						
					}
					break;
				case BlobElementType.ELEMENT_TYPE_VALUETYPE: 
					decompress = (int)Blob.DecompressBlobValue(blob,ref start_offset); //get number of parameters
			 		row = decompress>>2;
					if(row > 0)
					{
						int type = decompress&0x3;
						if(type == 0) //TypeDef
						{
							return (ClassDescriptor)new TypeDefClassDescriptor(assembly,BlobElementType.ELEMENT_TYPE_VALUETYPE,new Token(StreamTable.TYPEDEF_TABLE,row));
						}
						else if(type == 1) //TypeRef
						{
							return (ClassDescriptor)new TypeRefClassDescriptor(assembly,BlobElementType.ELEMENT_TYPE_VALUETYPE,new Token(StreamTable.TYPEREF_TABLE,row));
						}
					}
					break;
				case BlobElementType.ELEMENT_TYPE_GENERICINST:
					byte generic_type = blob[start_offset];
					start_offset++;
					uint arg_count;
					Token token;
					List<ClassDescriptor> generic_types;
					if(generic_type == BlobElementType.ELEMENT_TYPE_CLASS || 
						generic_type == BlobElementType.ELEMENT_TYPE_VALUETYPE)
					{
						token = TokenDecompressor.DecompressBlobTypeDefOrRefToken(blob,ref start_offset);
						arg_count = (uint)Blob.DecompressBlobValue(blob,ref start_offset);
						if(arg_count < 1)
							throw new CLELParserException("Genefic argument count: " + arg_count);
						generic_types = new List<ClassDescriptor>();
						for(i = 0;i < arg_count;i++)
						{
							generic_types.Add(ClassDescriptor.ParseMethodLocalTypeFromBlob(assembly,blob,ref start_offset));
						}

						if(token.Table == StreamTable.TYPEDEF_TABLE)
						{
							return (ClassDescriptor)new GenericTypeDefClassDescriptor(assembly,generic_type,token,generic_types);
						}
						else if(token.Table == StreamTable.TYPEREF_TABLE)
						{
							return (ClassDescriptor)new GenericTypeRefClassDescriptor(assembly,generic_type,token,generic_types);
						}
					}
					break;
				case BlobElementType.ELEMENT_TYPE_BYREF:
					ClassDescriptor cd = ClassDescriptor.ParseClassDescriptor(assembly,blob,ref start_offset);
					//byte table = blob[start_offset];
					//row = Bits.MakeInt(0,blob[start_offset+1],blob[start_offset+2],blob[start_offset+3]);
					//ELEMENT_TYPE_END
					//start_offset += 5;
					//return (ClassDescriptor)new ByRefClassDescriptor(new Token(table,row));
					return (ClassDescriptor)new ByRefClassDescriptor(cd);
				case BlobElementType.ELEMENT_TYPE_ARRAY:
					byte array_type = blob[start_offset];
					start_offset++;
					byte rank = blob[start_offset];
					start_offset++;
					byte [] bounds = new byte[rank];
					for(i = 0;i < rank;i++)
					{
						bounds[i] = blob[start_offset+i];
					}
					start_offset += rank;
					return (ClassDescriptor)new ArrayClassDescriptor(array_type,rank,bounds);
				case BlobElementType.ELEMENT_TYPE_SZARRAY:
					return (ClassDescriptor)new SZArrayClassDescriptor(ParseClassDescriptor(assembly,blob,ref start_offset));
				case BlobElementType.ELEMENT_TYPE_MVAR:
					uint num = (uint)Blob.DecompressBlobValue(blob,ref start_offset);
					return (ClassDescriptor)new MVarClassDescriptor(num);
			}
			//Console.WriteLine(start_offset);
			//Console.WriteLine(b);
			//Console.Write("TEST: ");
			//for(int k = 0;k < blob.Length;k++)
			//	Console.Write(blob[k]+" ");
			//Console.WriteLine();
			return null;
		}

		public static ClassDescriptor ParseMethodReturnTypeFromBlob(CLELAssembly assembly,byte [] blob,ref int start_offset)
		{
			return ParseClassDescriptor(assembly,blob,ref start_offset);
		}
		
		public static ClassDescriptor ParseMethodParameterTypeFromBlob(CLELAssembly assembly,byte [] blob,ref int start_offset)
		{
			return ParseClassDescriptor(assembly,blob,ref start_offset);
		}
		
		public static ClassDescriptor ParseFieldTypeFromBlob(CLELAssembly assembly,byte [] blob,ref int start_offset)
		{
			return ParseClassDescriptor(assembly,blob,ref start_offset);
		}

		public virtual ClassDescriptor Copy()
		{
			return null;
		}

		public virtual byte [] ToBytes()
		{
			return null;
		}
	}
}
